﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services.Description;
using System.Xml;
using System.ServiceModel;
using System.ServiceModel.Description;
using ServiceDescription = System.Web.Services.Description.ServiceDescription;
using ServiceDescriptionFormatExtension = System.Web.Services.Description.ServiceDescriptionFormatExtension;
using System.Collections;
using jade.content.onto;
using System.Reflection;
using Ontology2WSDL;
using List = java.util.List;
using jade.content.schema;
using java.util;
using System.Xml.Schema;
using jade.content.schema.facets;
using System.IO;

namespace Ontology2WSDL
{
    public class WSDLGenerator : Ontology2WSDL.IOntology2WSDL
    {
        private static String SOAP12 = "Soap12";
        private Ontology2WSDL.OntologyTypeBinder ontologyBinder = new Ontology2WSDL.OntologyTypeBinder();
        System.Collections.ArrayList allowedOperations = null;

        #region IOntology2WSDL Members

        
        public System.IO.MemoryStream getWSDLStream(Ontology ontology, String agentName, String assemblyName, string urlLocation)
        {
            if (ontology == null)
            {
                throw new Exception("Can't generate WSDL without Ontology. Please, set ontology at first");
            }
            if (agentName == null)
            {
                throw new Exception("Can't generate WSDL without Agent's name. Please, set agent name at first");
            }
            if (assemblyName == null)
            {
                throw new Exception("Can't generate WSDL without assemblyName. Please, set assemblyName at first");
            }

            MemoryStream outputStream = generateWSDL(ontology, agentName, assemblyName, urlLocation);
            return outputStream;

        }

        public void setAllowedOperations(System.Collections.ArrayList allowedOperations)
        {
            this.allowedOperations = allowedOperations;
        }

        #endregion


        public MemoryStream generateWSDL(Ontology ontology, String agentName, String assemblyName, string urlLocation)
        {
             // load an "empty" WSDL file (no types, messages, operations, ...)
            // to start from and add operations by code
            ServiceDescription wsdl = new ServiceDescription();
            
            wsdl.Namespaces.Add("tns", assemblyName);
            wsdl.Namespaces.Add("soap","http://schemas.xmlsoap.org/wsdl/soap/");
            wsdl.Namespaces.Add("s","http://www.w3.org/2001/XMLSchema");
            wsdl.Namespaces.Add("soap12","http://schemas.xmlsoap.org/wsdl/soap12/");
            wsdl.Namespaces.Add("http","http://schemas.xmlsoap.org/wsdl/http/");
            wsdl.Namespaces.Add("wsdl", "http://schemas.xmlsoap.org/wsdl/");

            wsdl.TargetNamespace = assemblyName;

            wsdl.Services.Clear();
            wsdl.Services.Add(getService(agentName, urlLocation));
            
            wsdl.Bindings.Clear();
            Binding binding = getBindWithoutOperations(agentName, assemblyName);

            wsdl.PortTypes.Clear();
            PortType portType = getPortTypesWithoutOperations(agentName);

            
            Types types = new Types();
            XmlSchema typesSchema =  new XmlSchema();
            typesSchema.TargetNamespace = assemblyName;
            typesSchema.ElementFormDefault = System.Xml.Schema.XmlSchemaForm.Qualified;
            
            
            Iterator actionIterator = ontology.getActionNames().iterator();
            // iterate all actions
            while(actionIterator.hasNext())
            {
                String actionName = (String)actionIterator.next();
                if (allowedOperations!=null && !allowedOperations.Contains(actionName))
                {
                    continue;
                }
                AgentActionSchema actionSchema = (AgentActionSchema)ontology.getSchema(actionName);                
                binding.Operations.Add(getOperationBinding(assemblyName, actionName));

                Operation operation = new Operation();
                operation.Name = actionName;

                operation.Messages.Add(getInputMessage(actionName));
                
                // add input message
                wsdl.Messages.Add(getMessage(actionSchema));
                typesSchema.Items.Add(getElementType(actionSchema));

                // add result message
                if (actionSchema.getResultSchema() != null)
                {
                    Message m = getMessage(actionSchema.getResultSchema());
                    // one result message can be used for more operations
                    // so insert it to the messages and types only once....
                    if (wsdl.Messages.Contains(m))
                    {
                        // add output message
                        wsdl.Messages.Add(m);
                        typesSchema.Items.Add(getElementType(actionSchema.getResultSchema()));
                    }
                    operation.Messages.Add(getOutputMessage(actionSchema.getResultSchema().getTypeName()));
                    

                }
                portType.Operations.Add(operation);
            }

            wsdl.Bindings.Add(binding);
            wsdl.PortTypes.Add(portType);
            types.Schemas.Add(typesSchema);
            wsdl.Types = types;

            MemoryStream stream = new MemoryStream();
            wsdl.Write(stream);
            return stream;
            
        }

        private XmlSchemaElement getElementType(TermSchema termSchema)
        {

            XmlSchemaElement element = new XmlSchemaElement();
            element.Name = termSchema.getTypeName();

            
            XmlSchemaComplexType complexType = new XmlSchemaComplexType();
            XmlSchemaSequence sequence = new XmlSchemaSequence();

            foreach (string paramName in termSchema.getNames())
            {
                ObjectSchema paramSchema = termSchema.getSchema(paramName);
                XmlSchemaElement sequenceElement = new XmlSchemaElement();
                sequenceElement.Name = paramName;
                sequenceElement.IsNillable = !termSchema.isMandatory(paramName);
                sequenceElement.MinOccurs = 1;
                sequenceElement.MaxOccurs = 1;

                sequenceElement.SchemaTypeName = new XmlQualifiedName("s:"+ontologyBinder.getXMLType(paramSchema.getTypeName()));

                // check cardinality
                Facet[] facets = termSchema.getFacets(paramName);
                foreach (Facet facet in facets)
                {
                    if (facet is CardinalityFacet)
                    {
                        CardinalityFacet cardinality = (CardinalityFacet)facet;
                        sequenceElement.MaxOccurs = cardinality.getCardMax();
                        sequenceElement.MinOccurs = cardinality.getCardMin();
                        break;
                    }
                }
                
                //sequenceElement.MinOccurs = paramSchema.
                sequence.Items.Add(sequenceElement);
            }
            

            complexType.Particle=sequence;

            element.SchemaType = complexType; 
            return element;

        }

        private PortType getPortTypesWithoutOperations(string agentName)
        {
            PortType portType = new PortType();
            portType.Name = agentName + SOAP12;            
            return portType;
        }

        private Binding getBindWithoutOperations(string agentName, string assemblyName)
        {
            Binding bind = new Binding();            
            bind.Name = agentName+SOAP12;
            bind.Type = new XmlQualifiedName("tns:" + agentName + SOAP12);

            
            Soap12Binding soapBinding = new Soap12Binding();
            soapBinding.Transport = "http://schemas.xmlsoap.org/soap/http";

            bind.Extensions.Add(soapBinding);

            return bind;
        }

        /// <summary>
        /// define webservice includes its URL
        /// </summary>
        /// <param name="serviceName"></param>
        /// <returns></returns>
        private Service getService(string serviceName, string urlLocation)
        {
            Service service = new Service();
            service.Name = serviceName;

            Port p = new Port();
            p.Binding = new XmlQualifiedName("tns:"+serviceName + SOAP12);
            p.Name = serviceName + SOAP12;
            p.Namespaces.Add("soap12", "http://schemas.xmlsoap.org/wsdl/soap12/");
            
            Soap12AddressBinding mySoapAddressBinding = new Soap12AddressBinding();
            mySoapAddressBinding.Location = urlLocation+serviceName+".agent";
                        
            p.Extensions.Add(mySoapAddressBinding);

            service.Ports.Add(p);

            return service;
        }

        private Message getMessage(TermSchema schema)
        {
            // add first message
            Message message = new Message();
            message.Name = schema.getTypeName();
            MessagePart part = new MessagePart();
            part.Name = "parameters";
            part.Element = new XmlQualifiedName("tns:"+schema.getTypeName());

            message.Parts.Add(part);
            return message;         
        }

        private MessagePart getMessagePart(string paramName, string paramType)
        {
            MessagePart messagePart = new MessagePart();
            messagePart.Name = paramName;
            messagePart.Type = new XmlQualifiedName(ontologyBinder.getXMLType(paramType));
            return messagePart;
        }

        private Operation getOperation(string operationName, string actionName, string outputType)
        {
            // add operation to port type
            Operation operation = new Operation();
            operation.Name = operationName;

            operation.Messages.Add(getInputMessage(operationName));
            operation.Messages.Add(getOutputMessage(operationName));

            return operation;
        }

        private OperationMessage getOutputMessage(string operationName)
        {
            OperationMessage outputMessage = (OperationMessage)new OperationOutput();
            outputMessage.Message = new XmlQualifiedName("tns:" + operationName );
            return outputMessage;
        }

        private OperationMessage getInputMessage(string operationName)
        {
            OperationInput inputMessage = new OperationInput();
            inputMessage.Message = new XmlQualifiedName("tns:" + operationName );
            return inputMessage;
        }

        private OutputBinding getOutputBinding()
        {
            // add output binding
            OutputBinding myOutput = new OutputBinding();
            Soap12BodyBinding soap12BodyOutputBinding = new Soap12BodyBinding();
            soap12BodyOutputBinding.Use = SoapBindingUse.Literal;
            myOutput.Extensions.Add(soap12BodyOutputBinding);
            return myOutput;            
        }

        private InputBinding getInputBinding()
        {
            // add input binding
            InputBinding myInput = new InputBinding();
            Soap12BodyBinding soap12BodyInputBinding = new Soap12BodyBinding();
            soap12BodyInputBinding.Use = SoapBindingUse.Literal;
            myInput.Extensions.Add(soap12BodyInputBinding);
            return myInput;
        }

        private OperationBinding getOperationBinding(string assemblyName, String operationName)
        {
            // add operation binding
            OperationBinding myOperationBinding = new OperationBinding();
            myOperationBinding.Name = operationName;
            Soap12OperationBinding myOperation = new Soap12OperationBinding();
            myOperation.Style = SoapBindingStyle.Document;
            myOperation.SoapAction = assemblyName + "." + operationName;
            myOperationBinding.Extensions.Add(myOperation);

            // set input binding
            myOperationBinding.Input = getInputBinding();
            // set output binding
            myOperationBinding.Output = getOutputBinding();

            return myOperationBinding;
        }





    }
}
